<?php

namespace App\Http\Controllers;

use App\Http\Controllers\Controller;
use  App\Models\Course;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;

/**
 * 视频有效期设置以及视频流输出（用于视频防盗）
 */
class VideoPlayController extends Controller
{
    //刷新过期时间的最低要求，单位分钟(比如视频过期时间小于设置分钟数时，过期时间延长；否则不变动)
    protected static $min_minute = 300; //5小时过期
    //新增的过期时间，单位分钟（重新设置视频多少分钟后过期无法使用）
    protected static $extend_minute = 60;
    //设置可以访问资源的api地址(需要先新建该方法，用于查询实际视频资源)
    protected static $video_api = 'videoPlay/play';
    //对应视频资源在表中字段名
    protected $field_name = 'video';
    //对应视频资源在表中字段名
    // protected $table = '';

    public function __construct()
    {
        parent::__construct();
    }
    /**
     * 获取播放视频的地址，并返回播放接口地址(获取播放地址)
     * @param $id  数据id
     * @param $table_name 数据对应表名
     */
    public function getVideoUrl($id, $table_name, $field_name = '')
    {
        //返回播放地址
        $rand = encrypt(['id' => $id, 'time' => time(), 'name' => $table_name, 'field' => $field_name], config('other.video_key'));
        $video_url = self::$video_api . '?rand=' . $rand;
        return $video_url;
    }

    /**
     * 播放视频资源接口(用户直接访问播放视频的接口)
     * @param $rand string 视频资源信息
     */
    public function play()
    {
        ini_set('memory_limit', '256M');
        $rand = $this->request->rand;
        if (empty($rand)) {
            header('HTTP/1.1 500 Internal Server Error');
            echo "Error: Video cannot be played !";
            exit();
        }
        $rand_data = decrypt($rand, config('other.video_key'));
        if (empty($rand_data) || empty($rand_data['id']) ||  empty($rand_data['time'])  || empty($rand_data['name']) || $rand_data['time'] < strtotime("-" . self::$min_minute . " min")) {
            header('HTTP/1.1 500 Internal Server Error');
            echo "Error: Video cannot be played !";
            exit();
        }
        $field_name = !empty($rand_data['field']) ? $rand_data['field'] : $this->field_name;
        $video = DB::table($rand_data['name'])->where('id', $rand_data['id'])->value($field_name);
        if (!$video) {
            header('HTTP/1.1 500 Internal Server Error');
            echo "Error: Video cannot be played !";
            exit();
        }

        $video = public_path('uploads/') . $video;
        $this->videoPutStream($video);


        //原始
        // $video2 = $this->getImgAddrUrl() . $video;
        // $this->videoPutStream2($video);

        // $video1 = public_path('uploads/') . $video;
        // $this->videoPutStream1($video1);

    }

    /**
     * 将视频资源，转为视频流输出  （浏览器可以对起进行下载）
     * @param $videoUrl 视频资源的完整路径地址
     */
    function videoPutStream1($videoUrl)
    {
        if (!$videoUrl) {
            header('HTTP/1.1 500 Internal Server Error');
            echo "Error: Video cannot be played !";
            exit();
        }

        // 设置HTTP头信息  
        $fileSize = filesize($videoUrl);
        $mimeType = mime_content_type($videoUrl); // 或者手动指定，如 'video/mp4'  

        header('Content-Type: ' . $mimeType);
        header('Content-Length: ' . $fileSize);
        header('Content-Disposition: inline; filename="video.mp4"'); // inline 表示尝试在浏览器中播放，而不是下载  
        header('X-Content-Type-Options: nosniff'); // 防止浏览器猜测内容类型  
        header('Cache-Control: no-cache, no-store, must-revalidate'); // 禁止缓存  
        header('Pragma: no-cache'); // 禁止缓存  
        header('Expires: 0'); // 立即过期  

        // 设置跨域头（如果需要的话）  
        header('Access-Control-Allow-Origin: *'); // 注意：'*' 允许所有来源，生产环境中应更严格地设置  

        // 以二进制读取方式打开文件  
        $handle = fopen($videoUrl, 'rb');

        if ($handle) {
            // 读取文件并输出到浏览器  
            while (!feof($handle)) {
                echo fread($handle, 8192);
                ob_flush(); // 刷新输出缓冲区  
                flush(); // 强制输出内容到浏览器  
            }
            fclose($handle);
        } else {
            // 处理文件打开失败的情况  
            http_response_code(500);
            echo 'Failed to open the video file.';
        }

        exit; // 确保没有其他内容被输出  
    }



    /**
     * 将视频资源，转为视频流输出  （可以抑制下载视频，不会卡） 兼容 ios 播放 
     * @param $videoUrl 视频资源的完整路径地址
     */
    function videoPutStream($videoUrl)
    {
        if (!$videoUrl) {
            header('HTTP/1.1 500 Internal Server Error');
            echo "Error: Video cannot be played !";
            exit();
        }
        //防止get_headers 报错end
        $fileSize = filesize($videoUrl);

        // 检查范围头部  在项目播放器里面 不会存在 这个  HTTP_RANGE  参数不存在的情况，但是浏览器访问就会出现 HTTP_RANGE  参数不存在的情况
        if (isset($_SERVER['HTTP_RANGE'])) {
            if (!preg_match('/^bytes=\d*-\d*(,\d*-\d*)*$/i', $_SERVER['HTTP_RANGE'])) {
                header('HTTP/1.1 500 Internal Server Error');
                echo 'Error: Video file could not be opened !';
                exit;
            }
            // 解析范围头部，这只是一个简单示例，实际可能需要更复杂的解析  
            list($_, $range) = explode('=', $_SERVER['HTTP_RANGE'], 2);
            list($start, $end) = explode('-', $range);
            $end = (empty($end)) ? ($fileSize - 1) : intval($end);
            $start = intval($start);

            // 设置 HTTP 响应头  
            header('HTTP/1.1 206 Partial Content');
            header('Content-Range: bytes ' . $start . '-' . $end . '/' . $fileSize);
            header('Content-Length: ' . ($end - $start + 1));
            header('Content-Type: video/mp4');

            //允许 https://draw.huilanyun.com 域名访问
            header("Access-Control-Allow-Origin: https://draw.huilanyun.com");
            // 允许的头信息
            header("Access-Control-Allow-Headers: X-Requested-With, Content-Type");
            // 允许的请求方法
            header("Access-Control-Allow-Methods: GET");

            // 打开视频文件并发送指定范围的数据  
            $file = fopen($videoUrl, 'rb');
            if ($file) {
                fseek($file, $start);
                $buffer = fread($file, $end - $start + 1);
                fclose($file);
                echo $buffer;
                exit;
            } else {
                // 如果文件无法打开，发送错误响应  
                header('HTTP/1.1 500 Internal Server Error');
                echo 'Error: Video file could not be opened !';
            }
        } else {
            // 如果没有范围头部，发送整个文件（这通常不是分段流的做法）  
            // ... 你可以在这里添加发送整个文件的代码  
            // 但通常，你应该返回一个 416 Requested Range Not Satisfiable 响应  
            // header('HTTP/1.1 416 Requested Range Not Satisfiable');
            // echo 'Requested range not satisfiable.';
            header('HTTP/1.1 404 Not Found');
            echo 'Error: Video file could not be opened !';   //（项目程序中不会出现 $_SERVER['HTTP_RANGE'] 不存在的情况，所以不考虑这种情况）
        }
    }



    /**
     * 将视频资源，转为视频流输出  （这种可以抑制下载视频，但是刚开始很卡）
     * @param $videoUrl 视频资源的完整路径地址
     */
    function videoPutStream2($videoUrl)
    {
        if (!$videoUrl) {
            header('HTTP/1.1 500 Internal Server Error');
            echo "Error: Video cannot be played !";
            exit();
        }
        //获取视频大小
        //防止get_headers 报错
        stream_context_set_default([
            'ssl' => [
                'verify_peer' => false,
                'verify_peer_name' => false,
            ],
        ]);
        //防止get_headers 报错end
        $header_array = get_headers($videoUrl, true);
        $sizeTemp = $header_array['Content-Length'];
        if (is_array($sizeTemp)) {
            $size = $sizeTemp[count($sizeTemp) - 1];
        } else {
            $size = $sizeTemp;
        }
        //初始参数
        $start = 0;
        $end = $size - 1;
        $length = $size;
        $buffer = 1024 * 1024 * 10; // 输出的流大小 10m

        //计算 Range
        $ranges_arr = array();
        if (isset($_SERVER['HTTP_RANGE'])) {
            if (!preg_match('/^bytes=\d*-\d*(,\d*-\d*)*$/i', $_SERVER['HTTP_RANGE'])) {
                header('HTTP/1.1 416 Requested Range Not Satisfiable');
            }
            $ranges = explode(',', substr($_SERVER['HTTP_RANGE'], 6));
            foreach ($ranges as $range) {
                $parts = explode('-', $range);
                $ranges_arr[] = array($parts[0], $parts[1]);
            }
            $ranges = $ranges_arr[0];

            $start = (int)$ranges[0];
            if ($ranges[1] != '') {
                $end = (int)$ranges[1];
            }
            $length = min($end - $start + 1, $buffer);
            $end = $start + $length - 1;
        }
        //会出现一段卡的情况
        else {
            // php 文件第一次浏览器请求不会携带 RANGE 为了提升加载速度 默认请求 1 个字节的数据
            $start = 0;
            $end = 1;
            $length = 2;
        }
        //添加 Range 分段请求
        $header = array("Range:bytes={$start}-{$end}");
        #发起请求
        $ch2 = curl_init();
        curl_setopt($ch2, CURLOPT_URL, $videoUrl);
        curl_setopt($ch2, CURLOPT_TIMEOUT, 60);
        curl_setopt($ch2, CURLOPT_HTTPHEADER, $header);
        //设置读取的缓存区大小
        curl_setopt($ch2, CURLOPT_BUFFERSIZE, $buffer);
        // 关闭安全认证
        curl_setopt($ch2, CURLOPT_SSL_VERIFYPEER, false);
        curl_setopt($ch2, CURLOPT_SSL_VERIFYHOST, false);
        //追踪返回302状态码，继续抓取
        curl_setopt($ch2, CURLOPT_HEADER, false);
        curl_setopt($ch2, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch2, CURLOPT_FOLLOWLOCATION, true);
        curl_setopt($ch2, CURLOPT_CONNECTTIMEOUT, 60);
        curl_setopt($ch2, CURLOPT_NOBODY, false);
        curl_setopt($ch2, CURLOPT_REFERER, $videoUrl);
        //模拟来路
        curl_setopt($ch2, CURLOPT_USERAGENT, "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.83 Safari/537.36 Edg/85.0.564.44");
        $content = curl_exec($ch2);
        curl_close($ch2);
        #设置响应头
        header('HTTP/1.1 206 PARTIAL CONTENT');
        header("Accept-Ranges: bytes");
        header("Connection: keep-alive");
        header("Content-Type: video/mp4");
        header('Cache-Control: max-age=3600, public'); // 设置缓存控制头部  
        header("Access-Control-Allow-Origin: *");
        //为了兼容 ios UC这类浏览器 这里加个判断 UC的 Content-Range 是 起始值-总大小减一
        if ($end != 1) {
            $end = $size - 1;
        }
        header("Content-Range: bytes {$start}-{$end}/{$size}");
        //设置流的实际大小
        header("Content-Length: " . strlen($content));
        //清空缓存区
        ob_clean();

        //输出视频流
        echo  $content;
        die;
        //销毁内存
        unset($content);
    }
}
